{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# 添加消息历史记录（内存）\n",
    "`RunnableWithMessageHistory` 允许我们为某些类型的链添加消息历史记录。它包装另一个 Runnable 并管理其聊天消息历史记录。\n",
    "\n",
    "具体来说，它可用于以下任何输入类型的 Runnable：\n",
    "\n",
    "- 一个 `BaseMessage` 序列\n",
    "- 一个以序列 `BaseMessage` 为值的字典\n",
    "- 一个以字符串或序列 `BaseMessage` 为最新消息的键和以历史消息为值的键的字典\n",
    "\n",
    "并且返回以下任何输出类型之一：\n",
    "\n",
    "- 一个可以作为 `AIMessage` 内容处理的字符串\n",
    "- 一个 `BaseMessage` 序列\n",
    "- 一个包含 `BaseMessage` 序列的字典\n",
    "\n",
    "让我们通过一些示例来看看它是如何工作的。首先，我们构建一个 Runnable（在这里接受字典作为输入并返回消息作为输出）：\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "145516bd13859046"
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "from dotenv import load_dotenv\n",
    "from langchain_community.llms.tongyi import Tongyi\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "load_dotenv()\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages([\n",
    "    (\n",
    "        \"system\",\n",
    "        \"你是一个擅长{ability}的助手。回答不超过20个字\",\n",
    "    ),\n",
    "    MessagesPlaceholder(variable_name=\"history\"),\n",
    "    (\"human\", \"{input}\"),\n",
    "])\n",
    "# 请注意，我们将max_retries = 0设置为避免在RateLimits等情况下重试\n",
    "llm1 = ChatOpenAI(\n",
    "    # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "    openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "    openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "    model_name=\"qwen-max\"\n",
    ")\n",
    "#\n",
    "llm2 = Tongyi()\n",
    "# 添加回退\n",
    "\n",
    "chain = prompt | llm1 | StrOutputParser()\n",
    "# chain.invoke({\"ability\": \"数学\", \"input\": \"余弦函数是什么意思？\"})"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-29T06:09:50.913392Z",
     "start_time": "2024-10-29T06:09:50.745568Z"
    }
   },
   "id": "24d872415882ae3",
   "execution_count": 64
  },
  {
   "cell_type": "markdown",
   "source": [
    "为了管理消息历史记录，我们需要：\n",
    "\n",
    "1. 这个 Runnable；\n",
    "2. 一个可调用对象，返回一个`BaseChatMessageHistory`实例。\n",
    "\n",
    "请查看[memory integrations](https://integrations.langchain.com/memory)页面，了解使用 Redis 和其他提供者实现聊天消息历史记录的方法。这里我们演示如何使用内存中的`ChatMessageHistory`，以及使用`RedisChatMessageHistory`进行更持久的存储。"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "20f2ea0310c358b6"
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 内存存储\n",
    " 聊天历史记录保存在内存中，这里使用全局 Python 字典。构建一个可调用的 `get_session_history`，它引用这个字典来返回一个 `ChatMessageHistory` 实例。可以通过在运行时向 `RunnableWithMessageHistory` 传递一个配置来指定可调用对象的参数。默认情况下，配置参数应该是一个单个字符串 `session_id`。可以通过 `history_factory_config` 关键字参数进行调整。"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "229a7e727a349883"
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [
    "from langchain_core.runnables import RunnableWithMessageHistory\n",
    "from langchain_core.chat_history import BaseChatMessageHistory\n",
    "from langchain_community.chat_message_histories import ChatMessageHistory\n",
    "\n",
    "store = {}\n",
    "\n",
    "\n",
    "def get_session_history(session_id: str) -> BaseChatMessageHistory:\n",
    "    \"\"\"\n",
    "    根据session_id获取会话历史message记录\n",
    "    :param session_id:  \n",
    "    :return: \n",
    "    \"\"\"\n",
    "    if session_id not in store:\n",
    "        print(\"创建新的session_id\")\n",
    "        store[session_id] = ChatMessageHistory()\n",
    "\n",
    "    print(store[session_id])\n",
    "    return store[session_id]\n",
    "\n",
    "\n",
    "with_message_history = RunnableWithMessageHistory(\n",
    "    runnable=chain,\n",
    "    # 通过history_factory参数指定消息历史记录的可调用对象  \n",
    "    get_session_history=get_session_history,\n",
    "    # 通过history_key参数指定消息历史记录的键  \n",
    "    history_messages_key=\"history\",\n",
    "    input_messages_key=\"input\",\n",
    ")"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-28T10:28:50.009717Z",
     "start_time": "2024-10-28T10:28:50.004615Z"
    }
   },
   "id": "664bed14dcdbb818",
   "execution_count": 40
  },
  {
   "cell_type": "markdown",
   "source": [
    "请注意，我们指定了 input_messages_key（要作为最新输入消息处理的键）和 history_messages_key（要添加历史消息的键）。\n",
    "\n",
    "在调用这个新的 chain 时，我们通过配置参数指定相应的聊天历史记录："
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "d75570d8022dfb57"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "创建新的session_id\n",
      "Human: 余弦函数是什么意思？\n",
      "AI: 余弦函数是三角函数的一种，表示角度的余弦值。\n"
     ]
    },
    {
     "data": {
      "text/plain": "'余弦函数是直角三角形中邻边比斜边的比值。'"
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "with_message_history.invoke({\n",
    "    \"ability\": \"数学\",\n",
    "    \"input\": \"余弦函数是什么意思？\",\n",
    "},\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}})\n",
    "\n",
    "# 记住\n",
    "with_message_history.invoke(\n",
    "    {\"ability\": \"数学\", \"input\": \"什么？\"},\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-28T10:28:55.310391Z",
     "start_time": "2024-10-28T10:28:50.011604Z"
    }
   },
   "id": "5e328653f41afc4c",
   "execution_count": 41
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 跟踪消息历史记录的配置参数\n",
    "跟踪消息历史记录的配置参数可以通过将 `ConfigurableFieldSpec` 对象列表传递给 `history_factory_config` 参数来自定义。下面，我们使用了两个参数：`user_id` 和 `conversation_id`。"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "163937390db09bea"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "data": {
      "text/plain": "'余弦函数是三角函数的一种，表示角度的余弦值。'"
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_core.runnables import ConfigurableFieldSpec\n",
    "\n",
    "store2 = {}\n",
    "\n",
    "\n",
    "def get_session_history(user_id: str, conversation_id: str) -> BaseChatMessageHistory:\n",
    "    if (user_id, conversation_id) not in store:\n",
    "        store2[(user_id, conversation_id)] = ChatMessageHistory()\n",
    "    return store2[(user_id, conversation_id)]\n",
    "\n",
    "\n",
    "with_message_history2 = RunnableWithMessageHistory(\n",
    "    runnable=chain,\n",
    "    # 通过history_factory参数指定消息历史记录的可调用对象  \n",
    "    get_session_history=get_session_history,\n",
    "    # 通过history_key参数指定消息历史记录的键  \n",
    "    history_messages_key=\"history\",\n",
    "    input_messages_key=\"input\",\n",
    "    history_factory_config=[\n",
    "        ConfigurableFieldSpec(\n",
    "            id=\"user_id\",\n",
    "            annotation=str,\n",
    "            name=\"用户 ID\",\n",
    "            description=\"用户的唯一标识符。\",\n",
    "            default=\"\",\n",
    "            is_shared=True,\n",
    "        ),\n",
    "        ConfigurableFieldSpec(\n",
    "            id=\"conversation_id\",\n",
    "            annotation=str,\n",
    "            name=\"对话 ID\",\n",
    "            description=\"对话的唯一标识符。\",\n",
    "            default=\"\",\n",
    "            is_shared=True,\n",
    "        )\n",
    "    ],\n",
    ")\n",
    "\n",
    "with_message_history2.invoke(\n",
    "    {\"ability\": \"数学\", \"input\": \"余弦函数是什么意思？\"},\n",
    "    config={\"configurable\": {\"user_id\": \"abc123\", \"conversation_id\": \"123\"}}, )"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-29T03:30:31.968171Z",
     "start_time": "2024-10-29T03:30:30.584538Z"
    }
   },
   "id": "60317cb5308a8882",
   "execution_count": 44
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 具有不同签名的 Runnable 的示例\n",
    "上述的 Runnable 接受字典作为输入并返回 BaseMessage。下面我们展示一些替代方案。\n",
    "\n",
    "### 输入为消息，输出为字典\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "b6eee630dd631967"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "data": {
      "text/plain": "{'output_message': AIMessage(content=\"Simone de Beauvoir 是20世纪法国著名的哲学家、作家和社会活动家，她与让-保罗·萨特（Jean-Paul Sartre）共同被认为是存在主义哲学的重要代表人物之一。在她的著作中，尤其是《第二性》(Le Deuxième Sexe) 和《伦理学的诱惑》(Pour une morale de l'ambiguïté)，de Beauvoir 探讨了自由意志的概念，并将其作为其哲学思想的核心部分。\\n\\nDe Beauvoir 认为，人类本质上是自由的存在。她继承和发展了存在主义关于“存在先于本质”的观点，强调个人选择的重要性以及每个人对自己生活的责任。根据这一理论，人的本质不是先天决定的，而是通过后天的选择和行动逐步形成的。因此，每个人都拥有自由去塑造自己的命运，尽管这种自由受到社会结构和个人环境等因素的影响。\\n\\n然而，de Beauvoir 也认识到，在现实生活中，人们并不总是能够完全行使他们的自由意志。性别不平等就是一个典型的例子，《第二性》中详细讨论了女性如何被社会构建的角色所限制，从而难以实现真正的自我解放和个人发展。她主张通过批判现有的权力关系和社会规范来促进更广泛的个体自由和平等。\\n\\n总之，Simone de Beauvoir 对自由意志的看法强调了个体选择的重要性及其对形成个人身份的关键作用，同时也指出了实现真正自由所需面对的社会障碍。\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 287, 'prompt_tokens': 28, 'total_tokens': 315, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'qwen-max', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-3e496b29-1365-43d6-bebb-e983e6844378-0', usage_metadata={'input_tokens': 28, 'output_tokens': 287, 'total_tokens': 315, 'input_token_details': {}, 'output_token_details': {}})}"
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "from langchain_core.messages import HumanMessage\n",
    "from langchain_core.runnables import RunnableParallel\n",
    "from langchain_community.llms.tongyi import Tongyi\n",
    "import os\n",
    "from langchain_openai import ChatOpenAI\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "load_dotenv()\n",
    "\n",
    "# 请注意，我们将max_retries = 0设置为避免在RateLimits等情况下重试\n",
    "llm1 = ChatOpenAI(\n",
    "    # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "    openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "    openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "    model_name=\"qwen-max\"\n",
    ")\n",
    "#\n",
    "llm2 = Tongyi()\n",
    "# 添加回退\n",
    "\n",
    "store3 = {}\n",
    "\n",
    "# 输出字典\n",
    "chain = RunnableParallel({\"output_message\": llm1})\n",
    "\n",
    "\n",
    "def get_session_history3(session_id: str) -> BaseChatMessageHistory:\n",
    "    if session_id not in store3:\n",
    "        store3[session_id] = ChatMessageHistory()\n",
    "\n",
    "    return store3[session_id]\n",
    "\n",
    "\n",
    "with_message_history3 = RunnableWithMessageHistory(\n",
    "    runnable=chain,\n",
    "    get_session_history=get_session_history3,\n",
    "    output_messages_key=\"output_message\",\n",
    ")\n",
    "\n",
    "with_message_history3.invoke(\n",
    "    # 输入为消息列表\n",
    "    [HumanMessage(content=\"Simone de Beauvoir 对自由意志的看法是什么\")],\n",
    "    # [(\"human\", \"Simone de Beauvoir 对自由意志的看法是什么\")],\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-29T05:29:44.912935Z",
     "start_time": "2024-10-29T05:29:24.380699Z"
    }
   },
   "id": "9aa9643de8be10b1",
   "execution_count": 52
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 输入为消息，输出为消息\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "eb452709867e3156"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "data": {
      "text/plain": "AIMessage(content=\"Simone de Beauvoir 对自由意志的看法深受存在主义哲学的影响，特别是在她与让-保罗·萨特的合作和对话中得到了充分的发展。她的观点可以总结为以下几个关键点：\\n\\n1. **存在先于本质**：\\n   - De Beauvoir 认同萨特的观点，即“存在先于本质”。这意味着人的本质不是先天决定的，而是通过个人的选择和行动逐渐形成的。\\n   - 人首先存在，然后通过自己的选择和行为来定义自己。这种观点强调了个体在塑造自身身份中的主动性和创造性。\\n\\n2. **自由与责任**：\\n   - 自由不仅是权利，也是责任。每个人必须为自己的选择负责，并面对这些选择带来的后果。\\n   - 这种责任感是人类存在的核心部分。de Beauvoir 强调，每个个体都必须承担起对自己生活和社会的责任。\\n\\n3. **社会结构的限制**：\\n   - 尽管 de Beauvoir 认为人本质上是自由的，但她也认识到社会结构和个人环境对自由意志的限制。\\n   - 在《第二性》（*Le Deuxième Sexe*）中，她详细探讨了女性如何被社会构建的角色所束缚，从而难以实现真正的自由。性别不平等和其他形式的社会压迫限制了个体的自由。\\n\\n4. **反抗与解放**：\\n   - De Beauvoir 提倡通过批判和反抗现有的权力关系和社会规范来促进个体的自由和平等。\\n   - 她认为，只有通过积极地改变那些限制自由的社会结构，才能实现真正的个人解放。这包括反对性别歧视、种族歧视和其他形式的社会不公。\\n\\n5. **他者的角色**：\\n   - 在《伦理学的诱惑》（*Pour une morale de l'ambiguïté*）中，de Beauvoir 探讨了他者（他人）在个体自由中的作用。\\n   - 个体的自由不仅受到自身选择的影响，还受到与他人的关系的影响。他者既是自由的障碍，也是自由的可能性之一。\\n   - 人与人之间的相互作用和相互依赖构成了一个复杂的网络，这个网络既提供了自由的可能性，也带来了限制。\\n\\n6. **情境中的自由**：\\n   - De Beauvoir 强调，自由是在具体情境中实现的。人们的选择总是嵌入在特定的历史、社会和文化背景中。\\n   - 因此，真正的自由不仅仅是抽象的概念，而是在具体的情境中通过具体的行动来实现的。\\n\\n7. **道德与自由**：\\n   - 在《伦理学的诱惑》中，de Beauvoir 探讨了自由与道德的关系。她认为，真正的自由必须伴随着对他人的尊重和关怀。\\n   - 个体的自由不应该以牺牲他人的自由为代价，而应该是一种相互支持和共同发展的过程。\\n\\n总之，Simone de Beauvoir 的自由意志观强调了个人选择的重要性、自由与责任的关系、以及社会结构对自由的限制。她主张通过批判和改变不公正的社会结构来实现更广泛的个体自由和平等。同时，她也强调了在具体情境中实现自由的重要性，并探讨了自由与道德之间的复杂关系。\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 653, 'prompt_tokens': 1381, 'total_tokens': 2034, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'qwen-max', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-8eb1a53c-3189-4341-8d91-33dcf9a8e98c-0', usage_metadata={'input_tokens': 1381, 'output_tokens': 653, 'total_tokens': 2034, 'input_token_details': {}, 'output_token_details': {}})"
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from operator import itemgetter\n",
    "\n",
    "RunnableWithMessageHistory(\n",
    "    runnable=itemgetter(\"input_messages\") | llm1,\n",
    "    get_session_history=get_session_history3,\n",
    "    input_messages_key=\"input_messages\",\n",
    ").invoke(\n",
    "    {\"input_messages\": [HumanMessage(content=\"Simone de Beauvoir 对自由意志的看法是什么\")]},\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-29T05:44:57.787008Z",
     "start_time": "2024-10-29T05:44:23.886873Z"
    }
   },
   "id": "4c7d46c08bbffdc8",
   "execution_count": 57
  },
  {
   "cell_type": "markdown",
   "source": [
    "# 持久化存储\n",
    "在许多情况下，持久化对话历史记录是更好的选择。`RunnableWithMessageHistory` 对于 `get_session_history` 可调用对象如何检索其聊天消息历史记录是不可知的。请参阅 [这里](https://github.com/langchain-ai/langserve/blob/main/examples/chat_with_persistence_and_user/server.py) 以了解如何使用本地文件系统的示例。下面我们演示如何使用 Redis。请查看 [memory integrations](https://integrations.langchain.com/memory) 页面，了解使用其他提供者实现聊天消息历史记录的方法。\n",
    "\n",
    "## 设置Redis\n",
    "1. 安装依赖`poetry add redis`\n",
    "2. Docker 安装redis：`docker run -d -p 6379:6379 --name redis redis`\n",
    "3. 配置到环境变量文件(`.env`)  `REDIS_URL = \"redis://localhost:6379/0\"`\n",
    "\n",
    "### 使用LangSmith\n",
    "LangSmith 对于诸如消息历史记录注入之类的操作非常有用，否则很难理解链的各个部分的输入是什么。\n",
    "\n",
    "请注意，LangSmith 不是必需的，但它很有帮助,注册[LangSmith](https://smith.langchain.com/)"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "6b70c097e35d087f"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "abc123\n",
      "abc123\n"
     ]
    },
    {
     "data": {
      "text/plain": "'余弦函数的反函数是反余弦函数，记作arccos或cos⁻¹。'"
     },
     "execution_count": 68,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "from langchain_community.chat_message_histories import RedisChatMessageHistory\n",
    "\n",
    "load_dotenv()\n",
    "def get_message_history_with_redis(session_id: str) -> RedisChatMessageHistory:\n",
    "    print(session_id)\n",
    "    return RedisChatMessageHistory(session_id=session_id, url=os.getenv(\"REDIS_URL\"),ttl=os.getenv(\"REDIS_TTL\"),key_prefix=\"se_\")\n",
    "\n",
    "\n",
    "with_message_history4 = RunnableWithMessageHistory(\n",
    "    runnable=chain,\n",
    "    get_session_history=get_message_history_with_redis,\n",
    "    history_messages_key=\"history\",\n",
    "    input_messages_key=\"input\",\n",
    ")\n",
    "\n",
    "with_message_history4.invoke(\n",
    "    {\"ability\": \"数学\", \"input\": \"余弦函数是什么意思？\"},\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")\n",
    "with_message_history4.invoke(\n",
    "    {\"ability\": \"数学\", \"input\": \"它的反函数是什么\"},\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-29T06:38:34.930550Z",
     "start_time": "2024-10-29T06:38:31.551450Z"
    }
   },
   "id": "bd4211a0ee0451c0",
   "execution_count": 68
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [],
   "metadata": {
    "collapsed": false
   },
   "id": "83c064e00cc0d35d"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
